package com.daodai.hjcjhsystemtd.service.impl;

import com.daodai.hjcjhsystemtd.mapper.TdengineMapper;
import com.daodai.hjcjhsystemtd.model.pojos.DeviceControlTdengineEntity;
import com.daodai.hjcjhsystemtd.model.pojos.OpcRequestNode;
import com.daodai.hjcjhsystemtd.model.pojos.TdengineEntity;
import com.daodai.hjcjhsystemtd.service.CacheInitService;
import com.daodai.hjcjhsystemtd.service.CollectionTaskService;
import com.daodai.hjcjhsystemtd.utils.CacheUtil;
import com.daodai.hjcjhsystemtd.utils.OpcUaClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * opc数据采集主体类
 * </p>
 *
 * @author 汪川贵
 * @since 2025-09-06
 */
@Service
@Slf4j
public class CollectionTaskServiceImpl implements CollectionTaskService {
    //设置数据采集的批量大小
    private final long batchSize = 1000;
    private long start = -999;
    private long end = 0;

    //设置控制数据采集的批量大小
    private final long controlBatchSize = 1000;
    private long controlStart = -999;
    private long controlEnd = 0;
    @Autowired
    private CacheInitService cacheInitService;

    @Autowired
    private CacheUtil cacheUtil;

    @Qualifier("opcUaClient37800")
    @Autowired
    private OpcUaClient opcUaClient37800;

    @Qualifier("opcUaClient37801")
    @Autowired
    private OpcUaClient opcUaClient37801;

    @Autowired
    private OpcUaClientUtil opcUaClientUtil;

    @Autowired
    private TdengineMapper tdengineMapper;

    //获取采集点位总数
    private Long nodeCount = null;
    //获取控制点位总数
    private Long controlCount = null;

    /**
     * 数据采集初始化任务
     */
    @Override
    @PostConstruct
    public void collectionInit() {
        //1.清除cache并将点位表信息加载到cache中
        cacheInitService.initNodeTableToCache();
        log.info("点位表初始化成功！！！");
        //2.将量程表信息加载到cache中
        cacheInitService.initNodeRange();
        nodeCount = cacheUtil.lLen("node_index");
        log.info("量程表初始化成功！！！");
        //3.将控制表信息加载到cache中
        cacheInitService.initControlTableToCache();
        controlCount = cacheUtil.lLen("control_node_index");
        log.info("设备控制表初始化成功！！！");
    }

    /**
     * 数据采集方法，可定时采集数据，也可按照一定频率采集数据
     */
    @Scheduled(fixedRate = 1000)//按照两秒的频率执行
    public void collectSchedule() {
        if (isNeedGetAll(start, end, nodeCount)) {
            start = start + controlBatchSize;
            end = nodeCount;
        } else {
            //模拟链表指针
            start = start + controlBatchSize;
            end = end + controlBatchSize;
        }
        //1.从cache中拿到需要采集的点位信息
        List<OpcRequestNode> nodeList = getNodeFromCache("node_index", start, end);
        if (nodeList == null || nodeList.size() < 1000) {
                start = -999;
                end  = 0;
        }
        //2.通过opc协议向opc服务端请求点位数据
        for (OpcRequestNode opcRequestNode : nodeList) {
            //使用split分割取出ns和s
            String[] part1 = opcRequestNode.getPassageDeviceNode().split(";");
            int ns = Integer.parseInt(part1[0].split("=")[1]);
            String s = part1[1].split("=")[1];
            try {
                DataValue value = null;//创建opc请求返回载体
                //判断该点位属于哪一个服务器端口
                if (opcRequestNode.getPort().equals("37800")) {

                    //调用工具类中的采集函数，返回结果实体,请求的端口为37800
                    value = opcUaClientUtil.readNodeValueWithReturn(opcUaClient37800, ns, s);
                } else {
                    //调用工具类中的采集函数，返回结果实体，请求的端口为37801
                    value = opcUaClientUtil.readNodeValueWithReturn(opcUaClient37801, ns, s);
                }
                //判断采集值是否符合规定
                if (value.getStatusCode() != null) {
                    //判断采集值是否符合量程规定
                    if (value.getStatusCode().isGood() || valueIsStandard(value, opcRequestNode.getDeviceTypeId())) {
//                            //量程运算
//                            JEP jep = new JEP();
//                            double x = Double.parseDouble(value.getValue().getValue().toString());
//                            jep.addVariable("x",x ); // 设置变量值
//                            jep.parseExpression(device.getRegular()); // 解析表达式
//                            //封装回实体类中
//                            device.setValue(jep.getValueAsObject().toString());

                        //3.将请求回的点位数据装回请求实体类OpcRequestNode，
                        opcRequestNode.setValue(value.getValue().getValue().toString());//重庆东不需要量程计算
//                        System.out.println("读取节点" + s + "成功，节点值为：" + value.getValue().getValue().toString() + ",时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
                    } else {
                       log.info("读取节点:{}失败，节点值为：null,时间：{}",s,LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

                    }
                } else {
                   log.info("读取节点:{}失败，节点值为：null,时间：{}",s,LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

                }
            } catch (Exception e) {
                log.error("读取该节点：{}时发送异常",s,e);
            }
        }
       log.info("读取1000条节点成功, 时间：{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
        log.info("开始进行数据插入..............., 时间：{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
        //4.将采集点位集合封装到入表集合
        List<TdengineEntity> tdengineEntityList = new ArrayList<>();
        nodeList.forEach(node -> {
            TdengineEntity tdengineEntity = new TdengineEntity();
            BeanUtils.copyProperties(node, tdengineEntity);
            tdengineEntity.setTs(new Date().getTime());
            //利用deviceId和nodeId组合成表名
            tdengineEntity.setTableName("'"+node.getDeviceId() + "_" + node.getNodeId()+"'");
            tdengineEntity.setDeviceTypeId("'"+node.getDeviceTypeId()+"'");
            tdengineEntity.setDeviceTypeName("'"+node.getDeviceTypeName()+"'");
            tdengineEntity.setNodeDescribe("'"+node.getNodeDescribe()+"'");
            tdengineEntity.setLocation(node.getLocation());
            tdengineEntityList.add(tdengineEntity);
        });

        //5.向tdengine数据库中批量插入数据
        try {
            writeToTdengine(tdengineEntityList);
            log.info("数据插入成功！时间：{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
        } catch (Exception e) {
          log.error("数据插入tdengine时发送异常",e);
        }

    }

    /**
     * 控制点位数据采集方法，可定时采集数据，也可按照一定频率采集数据
     */
    @Scheduled(fixedRate = 1000)//按照两秒的频率执行
    public void deviceControlCollectSchedule() {
        if (isNeedGetAll(controlStart, controlEnd, controlCount)) {
            controlStart = controlStart + batchSize;
            controlEnd = controlCount;
        } else {
            //模拟链表指针
            controlStart = controlStart + batchSize;
            controlEnd = controlEnd + batchSize;
        }
        //1.从cache中拿到需要采集的点位信息
        List<OpcRequestNode> nodeList = getControlNodeFromCache("control_node_index", controlStart, controlEnd);
        if (nodeList == null || nodeList.size() < 1000) {
            controlStart = -999;
            controlEnd  = 0;
        }
        //2.通过opc协议向opc服务端请求点位数据
        for (OpcRequestNode opcRequestNode : nodeList) {
            String s = opcRequestNode.getPassageDeviceNode();
            int ns = 2;
            try {
                DataValue value = null;//创建opc请求返回载体
                //判断该点位属于哪一个服务器端口
                if (opcRequestNode.getPort().equals("37800")) {

                    //调用工具类中的采集函数，返回结果实体,请求的端口为37800
                    value = opcUaClientUtil.readNodeValueWithReturn(opcUaClient37800, ns, s);
                } else {
                    //调用工具类中的采集函数，返回结果实体，请求的端口为37801
                    value = opcUaClientUtil.readNodeValueWithReturn(opcUaClient37801, ns, s);
                }
                //判断采集值是否符合规定
                if (value.getStatusCode() != null) {
                    //判断采集值是否符合量程规定
                    if (value.getStatusCode().isGood()) {

                        //3.将请求回的点位数据装回请求实体类OpcRequestNode，
                        opcRequestNode.setValue(value.getValue().getValue().toString());//重庆东不需要量程计算
//                        System.out.println("读取节点" + s + "成功，节点值为：" + value.getValue().getValue().toString() + ",时间：" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
                    } else {
                        log.info("读取控制节点: {}失败，节点值为：null,时间：{}",s,LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

                    }
                } else {
                    log.info("读取控制节点: {}失败，节点值为：null,时间：{}",s,LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));

                }
            } catch (Exception e) {
               log.error("读取该节点：{}时发送异常",s,e);
            }
        }
        log.info("读取1000条控制节点成功, 时间：{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
        log.info("开始进行数据插入..............., 时间：{}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
        //4.将采集点位集合封装到入表集合
        List<DeviceControlTdengineEntity> deviceControlTdengineEntityList = new ArrayList<>();
        nodeList.forEach(node -> {
            DeviceControlTdengineEntity deviceControlTdengineEntity = new DeviceControlTdengineEntity();
            BeanUtils.copyProperties(node, deviceControlTdengineEntity);
            deviceControlTdengineEntity.setTs(new Date().getTime());
            //利用deviceId和nodeId组合成表名
            deviceControlTdengineEntity.setTableName("'"+node.getDeviceId() + "_" + node.getNodeId()+"'");
            deviceControlTdengineEntity.setControlDeviceNode("'"+node.getPassageDeviceNode()+"'");
            deviceControlTdengineEntity.setNodeDescribe("'"+node.getNodeDescribe()+"'");
            deviceControlTdengineEntityList.add(deviceControlTdengineEntity);
        });

        //5.向tdengine数据库中批量插入数据
        try {
            tdengineMapper.writeCobtrolNodeToTdengine(deviceControlTdengineEntityList);
            log.info("数据插入成功！{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月d日 HH时mm分ss秒！")));
        } catch (Exception e) {
            log.error("插入tdengine时发送错误",e);
        }

    }

    /**
     * 从cache中获取控制点位信息
     * @param key
     * @param start
     * @param end
     * @return
     */
    private List<OpcRequestNode> getControlNodeFromCache(String key, long start, long end) {
        List<OpcRequestNode> nodeList = new ArrayList<>();
        //1.从cache索引集合中获取点位索引
        List<String> nodeIndexs = cacheUtil.lRange(key, start, end);
        //2.根据索引从hash点位表中获取请求点位信息
        for (String nodeIndex : nodeIndexs) {
            Map<Object, Object> map = cacheUtil.hGetAll(nodeIndex);
            OpcRequestNode opcRequestNode = new OpcRequestNode();
            opcRequestNode.setPort(map.get("port").toString());
            opcRequestNode.setPassageDeviceNode(map.get("controlDeviceNode").toString());
            opcRequestNode.setNodeId(map.get("nodeId").toString());
            opcRequestNode.setDeviceId(map.get("deviceId").toString());
            opcRequestNode.setNodeDescribe(map.get("nodeDescribe").toString());
            nodeList.add(opcRequestNode);
        }
        //3.将获取到的信息封装到实体类返回
        return nodeList;
    }

    /**
     * 判断cache内的点位是否取到低，取到低的话就将指针重置
     *
     * @return
     */
    private boolean isNeedGetAll(long start, long end, long nodeCount) {

        if (nodeCount - end < batchSize) {
            return true;
        }
        return false;
    }

    /**
     * tdengine写入方法
     *
     * @param tdengineEntityList
     */
    private void writeToTdengine(List<TdengineEntity> tdengineEntityList) {
//        int batchSize = 100;
//        int totalSize = tdengineEntityList.size();
//        for (int i = 0; i < totalSize; i += batchSize) {
//            int end = Math.min(i + batchSize, totalSize);
//            List<TdengineEntity> batchList = tdengineEntityList.subList(i, end);
            tdengineMapper.insertBatch(tdengineEntityList);
//        }
    }

    /**
     * @return
     * @param value
     * @param deviceTypeId
     */
    private boolean valueIsStandard(DataValue value, String deviceTypeId) {
        //1.从缓存中获取该类型点位的数值量程
        String valueRange = null;
        try {
            valueRange = cacheUtil.hGet("rangeTable:" + deviceTypeId, "valueRange").toString();
        } catch (Exception e) {
           log.error("从缓存中拿去点位量程时发送错误,deviceTypeId:{}",deviceTypeId,e);
            System.out.println(deviceTypeId+"----------------------------------------------------------");
        }
        //取出实体类中查询到的数值范围
        String[] part = valueRange.split("~");
        //转换得到的数值范围下限
        int minRange = Integer.parseInt(part[0]);
        //转换得到数值范围上限
        int maxRange = Integer.parseInt(part[1]);

        //2.将opc采集到的值与量程进行比较，是否符合,并返回结果
        //判断采集到的value值是否为空
        if (value.getValue().getValue() == null) {
            return false;
        }

        //如果采集到的值为布尔值，则不用判断量程
        if (value.getValue().getValue() instanceof Boolean) {
            return true;
        }
        //判断采集到的数据类型是否为浮点数
        if (value.getValue().getValue() instanceof Float) {
            float data = Float.parseFloat(value.getValue().getValue().toString());
            //与范围上下限进行比较判断，返回结果
            if (data >= minRange || data <= maxRange) {
                return true;
            } else {
                return false;
            }
        }
        //将采集到的值进行类型强转
        int data = Integer.parseInt(value.getValue().getValue().toString());

        //与范围上下限进行比较判断，返回结果
        if (data >= minRange || data <= maxRange) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * 获取cache中的请求点位信息
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    private List<OpcRequestNode> getNodeFromCache(String key, long start, long end) {
        List<OpcRequestNode> nodeList = new ArrayList<>();
        //1.从cache索引集合中获取点位索引
        List<String> nodeIndexs = cacheUtil.lRange(key, start, end);
        //2.根据索引从hash点位表中获取请求点位信息
        for (String nodeIndex : nodeIndexs) {
            Map<Object, Object> map = cacheUtil.hGetAll(nodeIndex);
            OpcRequestNode opcRequestNode = new OpcRequestNode();
            opcRequestNode.setPort(map.get("port").toString());
            opcRequestNode.setPassageDeviceNode(map.get("passageDeviceNode").toString());
            opcRequestNode.setDeviceTypeId(map.get("deviceTypeId").toString());
            opcRequestNode.setDeviceTypeName(map.get("deviceTypeName").toString());
            opcRequestNode.setNodeId(map.get("nodeId").toString());
            opcRequestNode.setNodeDescribe(map.get("nodeDescribe").toString());
            opcRequestNode.setDeviceId(map.get("deviceId").toString());
            opcRequestNode.setLocation(map.get("location").toString());
            nodeList.add(opcRequestNode);
        }
        //3.将获取到的信息封装到实体类返回
        return nodeList;
    }
}
